package com.jichangxiu.framework.security.filter;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.jichangxiu.common.constant.ErrorCode;
import com.jichangxiu.common.entity.bo.Payload;
import com.jichangxiu.common.entity.bo.Record;
import com.jichangxiu.common.entity.bo.Result;
import com.jichangxiu.common.entity.bo.SecurityUser;
import com.jichangxiu.common.exception.CaptchaException;
import com.jichangxiu.common.exception.FrameworkException;
import com.jichangxiu.common.exception.ServiceException;
import com.jichangxiu.common.properties.JcxProperties;
import com.jichangxiu.common.utils.*;
import com.jichangxiu.framework.factory.AsyncFactory;
import com.jichangxiu.framework.security.entity.AuthToken;
import com.jichangxiu.framework.service.LoginInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * LoginFilter:
 *
 * @author Jcx
 * @create 2023-04-04 13:41
 */
@Slf4j
public class LoginFilter extends AbstractAuthenticationProcessingFilter {

    private final LoginInfoService loginInfoService;

    private final JcxProperties jcxProperties;

    private final TokenUtils tokenUtils;

    public LoginFilter(LoginInfoService loginInfoService, JcxProperties jcxProperties, TokenUtils tokenUtils) {
        super(new AntPathRequestMatcher(jcxProperties.getLoginProperties().getLoginProcessingUrl(), HttpMethod.POST.name()));
        this.loginInfoService = loginInfoService;
        this.jcxProperties = jcxProperties;
        this.tokenUtils = tokenUtils;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("不支持的请求方式：" + request.getMethod());
        } else {
            Record logRecord = getLogRecord(request);
            Record record = Record.getRequestRecord(request);
            if (ObjectUtil.isEmpty(record.keySet()) || record.keySet().size() == 0) {
                throw ServiceException.build("未获取到有效参数");
            }
            String tenantName = record.getStr(jcxProperties.getLoginProperties().getParamTenantName());
            String username = record.getStr(jcxProperties.getLoginProperties().getParamUserInfo());
            String password = record.getStr(jcxProperties.getLoginProperties().getParamUserPass());
            if (StrUtil.isBlank(tenantName) || StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
                throw ServiceException.build("未获取到有效参数");
            }
            tenantName = tenantName.trim();
            username = username.trim();
            password = password.trim();

            logRecord.set("tenantId", tenantName);
            logRecord.set("userName", username);

            request.setAttribute("logRecord", logRecord);

            // 该处对第一步的token进行包装，用于在AuthenticationProvider里面校验是否该AuthenticationProvider拦截校验
            AuthToken authToken = new AuthToken(tenantName, username, password);

            this.setDetails(request, authToken);

            return this.getAuthenticationManager().authenticate(authToken);
        }
    }

    protected void setDetails(HttpServletRequest request, AuthToken jcxAuthToken) {
        jcxAuthToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    /**
     * 认证成功后处理
     *
     * @param httpServletRequest  请求
     * @param httpServletResponse 响应
     * @param chain               filter 控制对象
     * @param authentication      权限
     * @throws IOException      IO 异常
     * @throws ServletException 异常
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        Record logRecord = (Record) httpServletRequest.getAttribute("logRecord");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        // 生成JWT，并放置到请求头中
        try {
            SecurityUser securityUser = (SecurityUser) authentication.getDetails();
            String jti = tokenUtils.getLoginInfo(securityUser.getTenantId(), securityUser.getUsername());
            Record userInfo;
            Record tenantInfo;
            if (StrUtil.isEmpty(jti)) {
                // 创建
                Payload<SecurityUser> payload = tokenUtils.login(securityUser);
                userInfo = payload.getUserInfo().getRecord();
                tenantInfo = payload.getUserInfo().getTenant();
                jti = payload.getJti();
                CacheUtils.set(jti, payload);
            } else {
                // 如果有以前的就校验下是否快过期
                tokenUtils.verifyJwt(jti);
                Payload<SecurityUser> payload = tokenUtils.getPayload(jti);
                userInfo = payload.getUserInfo().getRecord();
                tenantInfo = payload.getUserInfo().getTenant();
                CacheUtils.set(jti, payload);
            }

            if (jcxProperties.getAuthProperties().getWriteJtiToHeader()) {
                httpServletResponse.setHeader(jcxProperties.getAuthProperties().getAuthHeaderKey(), jti);
            }

            userInfo.remove("password");
            userInfo.remove("superAdmin");

            Record record = Record.getRecord(jcxProperties.getAuthProperties().getJtiToBrowserKey(), jti)
                    .set(jcxProperties.getAuthProperties().getUserInfoToBrowserKey(), userInfo)
                    .set(jcxProperties.getAuthProperties().getTenantInfoToBrowserKey(), tenantInfo);
            Result<Record> result = Result.ok("登录成功", StrUtils.handleTime(record));

            logRecord.put("userName", securityUser.getUsername());
            logRecord.put("userId", securityUser.getUserId());
            logRecord.put("tenantId", securityUser.getTenantId());
            logRecord.put("msg", "登录成功");
            logRecord.put("status", true);

            // 异步记录登录日志
            AsyncUtils.getINSTANCE().execute(AsyncFactory.recordPlatformLoginInfo(loginInfoService, logRecord));

            outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
            outputStream.close();
        } catch (Exception e) {
            Record record = Record.getRequestRecord(httpServletRequest);
            String username = record.getStr(jcxProperties.getLoginProperties().getParamUserInfo());
            logRecord.put("userName", username);
            logRecord.put("userId", "0");
            logRecord.put("tenantId", "0");
            logRecord.put("msg", "登录失败：" + e.getMessage());
            logRecord.put("status", false);

            // 异步记录登录日志
            AsyncUtils.getINSTANCE().execute(AsyncFactory.recordPlatformLoginInfo(loginInfoService, logRecord));

            throw FrameworkException.build(e.getMessage(), e);
        }
    }

    /**
     * 认证失败后处理
     *
     * @param httpServletRequest  请求
     * @param httpServletResponse 响应
     * @param exception           异常
     * @throws IOException      异常
     * @throws ServletException 异常
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException exception) throws IOException, ServletException {
        Record logRecord = getLogRecord(httpServletRequest);
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        String errorMessage = ErrorCode.USERNAME_PASSWORD_ERROR.getMsg();
        Result<Object> result;
        if (exception instanceof CaptchaException) {
            errorMessage = ((CaptchaException) exception).getMsg();
            result = Result.no(ErrorCode.CAPTCHA_ERROR).MSG(errorMessage);
        } else {
            if (StrUtils.isContainChinese(exception.getMessage())) {
                result = Result.no(exception.getMessage());
            } else {
                result = Result.no(errorMessage);
            }
        }

        Record record = Record.getRequestRecord(httpServletRequest);
        String username = record.getStr(jcxProperties.getLoginProperties().getParamUserInfo());
        logRecord.put("userName", username);
        logRecord.put("userId", "0");
        logRecord.put("tenantId", "0");
        logRecord.put("msg", errorMessage);
        logRecord.put("remark", "登录失败");
        logRecord.put("status", false);

        // 异步记录登录日志
        AsyncUtils.getINSTANCE().execute(AsyncFactory.recordPlatformLoginInfo(loginInfoService, logRecord));

        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }

    private Record getLogRecord(HttpServletRequest httpServletRequest) {
        // 设置方法名称
        Record record = Record.getRecord();
        String ipAddr = IpUtils.getIpAddr(httpServletRequest);
        record.put("ip", ipAddr);
        record.put("browser", IpUtils.getBrowser(httpServletRequest));
        record.put("os", IpUtils.getOs(httpServletRequest));
        return record;
    }

}
